Skip to content

source_hsolver: decouple the eigensolvers from hamilt through HSOperator/HSMatrix interfaces - #7974

Merged
mohanchen merged 5 commits into
deepmodeling:developfrom
Critsium-xy:refactor/hsolver-hamilt-decouple
Sep 18, 2026
Merged

mohanchen merged 5 commits into
deepmodeling:developfrom
Critsium-xy:refactor/hsolver-hamilt-decouple

Conversation

@Critsium-xy

@Critsium-xy Critsium-xy commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Reminder

  • I have read AGENTS.md and docs/developers_guide/agent_governance.md.
  • I have linked an issue or explained why this PR does not need one.
  • I have added adequate unit tests and/or case tests, or explained why not.
  • I have listed the exact verification commands run and their results.
  • I have described user-visible behavior changes, including INPUT parameter changes.
  • I have explained core-module impact for ESolver, HSolver, ElecState, Hamilt, Operator, Psi, or other source/ changes.
  • I have requested any needed governance exception below.

Linked Issue

No issue. This is one step of the ongoing module-dependency cleanup: source_hsolver should only do the math of the eigenproblem and not know hamilt::Hamilt.

Unit Tests and/or Case Tests for my changes

  • Commands run:
    • cmake -B build -DENABLE_LCAO=ON -DENABLE_MPI=ON -DENABLE_ELPA=ON -DENABLE_LIBRI=ON -DBUILD_TESTING=ON && cmake --build build --target abacus_std_para MODULE_HSOLVER_*
    • cp source/source_hsolver/test/*.dat source/source_hsolver/test/*.sh build/source/source_hsolver/test/ && cd build && OMP_NUM_THREADS=1 ctest -R MODULE_HSOLVER --output-on-failure
    • Integration cases (bash ../integrate/Autotest.sh -a build/abacus_std_para -n 4 -o 2 -r <regex>) chosen to hit every changed path: 01_PW: 022_PW_CG, 023_PW_DA, 025_PW_DS_sca, 038_PW_NC, 057_PW_SO_IW, 212_PW_USPP_BPCG, 089_PW_get_wf_kpar_bndpar, scf_deltaspin2 (PW deltaspin); 02_NAO_Gamma: scf_FeBiTe (genelpa); 03_NAO_multik: scf_pp_sg15 (kpar), scf_deltaspin2 (LCAO deltaspin); 10_others: 01_NP_KP_sp (lcao_in_pw); 06_SDFT: 01_PW_SDFT_10S_M.
    • CUDA build (2× RTX 3090, CUDA 12.9): cmake -B build_cuda -DUSE_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=86 <same flags> && cmake --build build_cuda --target abacus_std_gpu MODULE_HSOLVER_*, then OMP_NUM_THREADS=1 ctest -R MODULE_HSOLVER (21 tests incl. MODULE_HSOLVER_LCAO_cusolver), and GPU cases 11_PW_GPU: scf_cg, scf_cg_single, scf_dav, scf_dav_sub, scf_bpcg (-n 2); 13_NAO_multik_GPU: all 3; 12_NAO_Gamma_GPU: 001_NO_BiSeCuO_GPU, 009_NO_Si2_DZP_GPU, 012_NO_Si2_DZP_S2_GPU (all ks_solver cusolver); plus 01_PW/scf_deltaspin2 rerun with device gpu (no registered GPU deltaspin case exists) to exercise the GPU branches of cal_mw_from_lambda.cpp / deltaspin_pw_mi.cpp.
  • Full 01_PW suite run locally with the final binaries: CPU build (-n 4, as CI) passes all 845 property checks; the only cases that exit early are the LibXC/SCAN and precision single ones, which this machine's build does not support. CUDA build with CI's "01_PW on GPU" recipe (device rewritten to gpu, -n 1, CASES_GPU.txt): all property checks pass including 097_PW_PBE0_FM; the only failures are cases whose kpar needs more than one rank.
  • Result summary: abacus_std_para builds with __EXX on (so the HamiltLIPHSOperator EXX hooks compile); 19/19 CPU and 21/21 CUDA MODULE_HSOLVER_* unit tests pass, including the MPI parallel scripts (cg, dav, LCAO, k2d); all 14 CPU integration cases and all 11 GPU cases above pass against their reference results; the GPU deltaspin run reproduces the CPU reference (etot to 1e-9 Ry, identical force/stress).
  • Checks not run, with reason:
    • parakSolve_cusolver (multi-rank ks_solver cusolver) was compiled but not run; the GPU cusolver cases are single-rank. ROCm/DCU not available.

What's changed?

Developer-facing only; no INPUT parameter or numerical behavior change is intended.

The eigensolvers used to see the Hamiltonian either as std::function callbacks built from hamilt::Hamilt inside HSolverPW::hamiltSolvePsiK (iterative PW solvers), or as a hamilt::Hamilt* directly (HSolverPW/LCAO/LIP, DiagoIterAssist, Parallel_K2D). Community feedback was that the std::function/lambda plumbing is hard to read. Both are replaced by two small pure-virtual interfaces that carry only what the math needs:

  • hsolver::HSOperator<T, Device> (source_hsolver/hs_operator.h): update_k / hpsi / spsi, plus two optional subspace hooks with empty defaults (add_to_subspace_h, export_subspace_vec, used only by lcao_in_pw EXX). Consumed by DiagoCG, DiagoDavid, Diago_DavSubspace, DiagoBPCG, DiagoIterAssist, HSolverPW, HSolverLIP.
  • hsolver::HSMatrix<T> (source_hsolver/hs_matrix.h): hs_at_k(ik, hk, sk). Consumed by HSolverLCAO and Parallel_K2D (its HskFunc std::function is gone).

The single adapter layer lives in source_hamilt/hamilt_hs_adapter.h: hamilt::HamiltHSOperator (wraps Hamilt* + PW_Basis_K*, the only place that turns raw pointers into Psi/hpsi_info) and hamilt::HamiltHSMatrix (updateHk + matrix). hamilt::HamiltLIPHSOperator (in hamilt_lcaopw.h) implements the EXX hooks that HSolverLIP used to reach through a dynamic_cast<HamiltLIP>; HSolverLIP::solve loses its General_Exx_Info argument. module_lr gets a 20-line LRHSOperator because HamiltLR/HamiltULR never were hamilt::Hamilt.

hsolver_pw.h, hsolver_lcao.h, hsolver_lcaopw.h and diago_iter_assist.h no longer include source_hamilt/hamilt.h. Callers (ESolver_KS_PW, ESolver_KS_LCAO, ESolver_KS_LCAO_TDDFT, ESolver_KS_LIP, LCAO_domain::set_psi_occ_dm_chg, deltaspin, PSI_Prepare) construct the adapter and pass it in. HSolverPW_SDFT still takes a hamilt::Hamilt* (it depends on module_stodft, which itself depends on hamilt) and builds the adapter internally; that is left for a follow-up.

Small semantic notes for reviewers:

  • DiagoCG's subspace step keeps solving the generalized problem (hegvd) on restarts, exactly as the old subspace_func callback did (it accepted an S_orth flag but never used it). Passing the flag through to heevx changes eigenvector phases and broke the Wannier90 projections of 101_PW_W90 in the first CI run; the second commit restores the old behaviour with a comment.
  • The adapter's spsi always passes npw (without npol) as the plane-wave count to Hamilt::sPsi, matching what HSolverPW did. DiagoIterAssist::diag_subspace_init (CPU) used to pass nbasis and diag_subspace used to pass ngk; the value is only read on the USPP path where vkb has npw rows.
  • diag_subspace_init no longer copies the input into a temporary Psi before calling hPsi; the operator takes the pointer directly.
  • The "Operators in Hamilt are not allocated yet" fallback in diag_subspace_init (print a severe warning, copy psi, return zero eigenvalues) is now a WARNING_QUIT inside HamiltHSOperator::hpsi.

Review feedback addressed (@mohanchen): HSolverLIP::solve carried dead tpiba and nat parameters. They are dead in HSolverPW::solve too — both predate this PR, and nothing in either body reads them — so both pairs are removed, together with the arguments at every call site (ESolver_KS_PW, ESolver_KS_LIP, the CPU and GPU deltaspin PW solves, and the SolveLcaoInPW unit test). No behaviour change.

Tests: the seven iterative-solver tests drive the solvers with an HSOperatorMock<T> over the dense test matrix instead of the HamiltPW/OperatorMock specialisations in diago_mock.h, and no longer link operator.cpp/op_pw.cpp. test_hsolver_pw.cpp's lcao_in_pw test previously covered only the removed fallback; it now checks the subspace rotation with H = S = 1 (eigenvalues 1, rotated psi orthonormal) on full-rank input.

Governance Notes

  • INPUT/docs changes: none.
  • Core module impact: HSolver (all iterative and direct solver entry points change signature), Hamilt (new header-only adapter, no change to hamilt::Hamilt itself), ESolver/Psi/deltaspin/LR call sites updated accordingly.
  • Exceptions requested: none.

🤖 Generated with Claude Code

Critsium-xy and others added 3 commits September 16, 2026 16:10
…terfaces

The eigensolvers in source_hsolver used to see the Hamiltonian either as
std::function callbacks built from hamilt::Hamilt (iterative PW solvers) or
as hamilt::Hamilt* directly (HSolverPW/LCAO/LIP, DiagoIterAssist,
Parallel_K2D). Both are replaced by two small abstract interfaces that carry
only what the math needs:

- hsolver::HSOperator<T, Device>: update_k / hpsi / spsi, plus two optional
  subspace hooks (used by lcao_in_pw EXX). Consumed by DiagoCG, DiagoDavid,
  Diago_DavSubspace, DiagoBPCG, DiagoIterAssist, HSolverPW and HSolverLIP.
- hsolver::HSMatrix<T>: hs_at_k(ik, hk, sk). Consumed by HSolverLCAO and
  Parallel_K2D (its HskFunc std::function is gone).

hamilt::HamiltHSOperator / hamilt::HamiltHSMatrix (source_hamilt/
hamilt_hs_adapter.h) are the only place that wraps raw pointers into
Psi/hpsi_info for the operator chain; HamiltLIPHSOperator adds the EXX
subspace hooks that HSolverLIP used to reach through a dynamic_cast.
LR-TDDFT gets its own LRHSOperator since HamiltLR is not a hamilt::Hamilt.

hsolver_pw.h, hsolver_lcao.h, hsolver_lcaopw.h and diago_iter_assist.h no
longer include source_hamilt/hamilt.h. HSolverPW_SDFT still takes a Hamilt
(it depends on module_stodft) and is left for a follow-up.

Tests: the iterative solver tests drive the solvers with an HSOperatorMock
over the dense test matrix instead of a HamiltPW/OperatorMock, and no
longer link operator.cpp/op_pw.cpp. The lcao_in_pw test previously
exercised the "no operators allocated" fallback, which is now a hard error
in the adapter; it now checks the subspace rotation with H = S = 1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Operator

- DiagoCG: the old subspace_func callback in HSolverPW ignored the S_orth
  flag and always solved the generalized subspace problem (hegvd). Passing
  the flag through switched CG restarts to heevx, which changes eigenvector
  phases and broke the Wannier90 projections of 101_PW_W90. Always solve
  the generalized problem, as before.
- pyabacus: the Davidson adapters still built std::function callbacks for
  DiagoDavid / Diago_DavSubspace. Replace them with PyHSOperator, an
  HSOperator over the Python matrix-vector callable (S = identity).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mohanchen

Copy link
Copy Markdown
Collaborator

Dead parameterstpiba andnat inHSolverLIP::solve (non-blocking) hsolver_lcaopw.h:46-47

@mohanchen mohanchen added Refactor Refactor ABACUS codes Diago Issues related to diagonalizaiton methods The Absolute Zero Reduce the "entropy" of the code to 0 labels Sep 17, 2026
Critsium-xy and others added 2 commits September 18, 2026 13:34
Review feedback on deepmodeling#7974: `HSolverLIP::solve` still takes `tpiba` and `nat`,
which no code in its body reads. The same two parameters are equally dead in
`HSolverPW::solve`; both were left over from an earlier PW/EXX path.

Remove them from the declarations, the definitions and every call site
(`ESolver_KS_PW`, `ESolver_KS_LIP`, the CPU and GPU deltaspin PW solves, and
the `SolveLcaoInPW` unit test). No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mohanchen
mohanchen merged commit 882f825 into deepmodeling:develop Sep 18, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Diago Issues related to diagonalizaiton methods Refactor Refactor ABACUS codes The Absolute Zero Reduce the "entropy" of the code to 0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants